<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>

    <style>
      .canvas-container {
        position: relative;
        margin: 50px auto;
        box-shadow: 1px 3px 5px #ccc;
      }

      .canvas-container .round {
        opacity: 0;
        position: absolute;
        top: 0;
        left: 0;
        z-index: 1;
        display: block;
        width: 6px;
        height: 6px;
        background-color: #85c35e;
        border-radius: 50%;
        box-shadow: 0 0 3px #85c35e;
        
      }

      .canvas-container .round.show {
        opacity: 1;
        transform: scale(1.5);
        transition: transform .5;
      }
    </style>
  </head>
  <body>
    
    <div class="canvas-container">
      <i class="round"></i>
      <canvas id="chartCanvas"></canvas>
    </div>

    <script>
      const data = {
        axis1: {
          name: "降水量",
          unit: "ml",
          data: [ 6, 32, 70, 86, 68.7, 100.7, 125.6, 112.2, 78.7, 48.8, 36.0, 19.3 ],
          max: 250,
        },
        axis2: {
          name: "温度",
          unit: "°C",
          max: 25,
          data: [ 6.0, 10.2, 10.3, 11.5, 10.3, 13.2, 14.3, 16.4, 18.0, 16.5, 12.0, 5.2 ],
        },
        bottom: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
      };

      const oContainer = document.querySelector('.canvas-container');
      const oCan = document.getElementById('chartCanvas');
      const oRound = oContainer.querySelector('.round');
      const ctx = oCan.getContext('2d');

      const canPaddingX = 80;
      const canPaddingY = 70;
      const axisXGap = 40;
      const bottomTextGap = 60;
      const lineCount = 6;
      const totalHeight = (lineCount - 1) * axisXGap;
      const axisXWidth = axisXGap * 2 + (data.bottom.length - 1) * bottomTextGap;
      const canWidth = axisXWidth + canPaddingX * 2;
      const canHeight = totalHeight + canPaddingY * 2;

      let xGap = canPaddingY;
      let leftNumber = data.axis2.max;
      let rightNumber = data.axis1.max;
      let textGap = bottomTextGap;

      const roundData = [];

      const init = () => {
        adaptDpr(oCan, canWidth, canHeight);
        bindEvent();
        setDefaultStyle();
        drawAxisField();
        drawAxisLines();
        drawRounds();
        drawRoundLines();
        drawRects();
      }

      function bindEvent () {
        oCan.addEventListener('mousemove', handleCanvasMouseMove, false);
      }

      function handleCanvasMouseMove (e) {
        const x = e.clientX;
        const y = e.clientY;

        const roundPos = checkInPath(x, y);

        if (roundPos.length) {
          oRound.style.left = roundPos[0] - 3 + 'px';
          oRound.style.top = roundPos[1] - 3 + 'px';
          oRound.classList.add('show');
        } else {
          oRound.classList.remove('show');
        }
      }

      function setDefaultStyle () {
        ctx.lineCap = 'round';
        ctx.lineJoin = 'round';
        ctx.font = '14px Arial';
      }

      function drawAxisField () {
        const textPosY = xGap - 30;
          
        ctx.fillStyle = '#666';
        
        ctx.fillText(data.axis2.name, canPaddingX - 30, textPosY);
        ctx.fillText(data.axis1.name, axisXWidth + canPaddingX - 10, textPosY);
      }

      function drawAxisLines () {
        for (let i = 0; i < lineCount; i ++) {
          const isFinalLine = i === lineCount - 1;

          drawAxisLine(isFinalLine);

          if (isFinalLine) {
            for (let i = 0; i < data.bottom.length; i ++) {
              drawBottomText(i);
            }
          }
        }
      }

      function drawAxisLine (isFinalLine) {

        const leftText = leftNumber + ' ' + data.axis2.unit;
        const rightText = rightNumber + ' ' + data.axis1.unit;

        if (isFinalLine) {
          ctx.strokeStyle = '#333';
          ctx.setLineDash([]);
        } else {
          ctx.strokeStyle = '#999';
          ctx.setLineDash([5, 5]);
        }

        ctx.textBaseline = 'middle';
        ctx.fillStyle = '#666';

        ctx.beginPath();

        ctx.moveTo(canPaddingX, xGap);
        ctx.lineTo(axisXWidth + canPaddingX, xGap);
        ctx.textAlign = 'end';
        ctx.fillText(leftText, canPaddingX - 10, xGap);
        ctx.textAlign = 'start';
        ctx.fillText(rightText, axisXWidth + canPaddingX + 10, xGap);
        ctx.stroke();
        
        leftNumber -= 5;
        rightNumber -= 50;
        xGap += axisXGap;
      }

      function drawRounds () {
        for (let i = 0; i < data.axis2.data.length; i ++) {
          drawRound(i);
        }
      }

      function drawRoundLines () {
        for (let i = 0; i < roundData.length - 1; i ++) {
          drawRoundLine(i);
        }
      }

      function drawRects () {
        for (let i = 0; i < data.axis1.data.length; i ++) {
          drawRect(i);
        }
      }

      function drawRound (index) {
        const [ x, y ] = cToPx(index);

        roundData.push([ x, y ]);

        ctx.fillStyle = '#85c35e';
        ctx.beginPath();
        ctx.arc(x, y, 3, 0, 2 * Math.PI);
        ctx.fill();
      }

      function drawRoundLine (index) {
        const [ x1, y1 ] = roundData[index];
        const [ x2, y2 ] = roundData[index + 1];

        ctx.strokeStyle = '#85c35e';
        ctx.beginPath();
        ctx.moveTo(x1, y1);
        ctx.lineTo(x2, y2);
        ctx.stroke();
      }

      function drawBottomText (index) {
        const text = data.bottom[index];
        const startX = canPaddingX + axisXGap + index * textGap;
        const textWidth = ctx.measureText(text).width;

        ctx.beginPath();
        ctx.moveTo(startX, xGap - axisXGap);
        ctx.lineTo(startX, xGap - axisXGap + 5);
        ctx.fillText(text, startX - textWidth / 2, xGap - axisXGap + 20);
        ctx.stroke();
      }

      function drawRect (index) {
        const [ x, y ] = mlToPx(index);

        ctx.fillStyle = '#5a6fc0';

        ctx.beginPath();
        ctx.fillRect(x - 20, y, 40, totalHeight + canPaddingY - y);
      }


      function adaptDpr (canvas, width, height) {
        const dpr = window.devicePixelRatio;

        canvas.width = Math.floor(width * dpr);
        canvas.height = Math.floor(height * dpr);
        oContainer.style.width = width + 'px';
        oContainer.style.height = height + 'px';
        canvas.style.width = width + 'px';
        canvas.style.height = height + 'px';
        ctx.scale(dpr, dpr);
      }

      function cToPx (index) {
        const x = canPaddingX + axisXGap + index * bottomTextGap;
        const y = totalHeight - totalHeight * data.axis2.data[index] / data.axis2.max + canPaddingY;
        
        return [ x, y ];
      }

      function mlToPx (index) {
        const x = canPaddingX + axisXGap + index * bottomTextGap;
        const y = totalHeight - totalHeight * data.axis1.data[index] / data.axis1.max + canPaddingY;
        
        return [ x, y ];
      }

      function checkInPath (x, y) {
        for (let i = 0; i < roundData.length; i ++) {
          const [ rx, ry ] = roundData[i];
          const _x = rx + oContainer.offsetLeft;
          const _y = ry + oContainer.offsetTop;

          if (
            x >= _x - 3 &&
            x <= _x + 3 &&
            y >= _y - 3 &&
            y <= _y + 3
          ) {
            return [ rx, ry ];
          }
        }

        return [];
      }

      /*
          totalHeight / max = x / data
          totalHeight - totalHeight * data / max + canPaddingX
      
      */

      init();
    </script>
  </body>
</html>
